//
//  GoogleReader.m
//  Feeder
//
//  Created by Diego Acosta on 8/1/10.
//  Copyright 2010 __MyCompanyName__. All rights reserved.
//

#import "GoogleReader.h"
#import "WebServiceInterfaceProtected.h"
#import "GoogleReaderHelper.h"
#import "JSON.h"
#import "FormatHelper.h"

#define kMaxCountDefault 1000;


@interface GoogleReader(Private)

- (void)_performActions;
- (void)_setRequestActionList:(RequestAction)firstAction, ...;

- (void)_processResponse:(NSDictionary *)dict;

- (void)_updateTags:(NSDictionary *)dict;
- (void)_updateSubs:(NSDictionary *)dict;
- (void)_updateStreamPrefs:(NSDictionary *)dict;
- (void)_updateUnreadCounts:(NSDictionary *)dict;
- (void)_updateFeedItems:(NSDictionary *)dict;

@end

@implementation GoogleReader

@synthesize allunreads;
@synthesize tags;
@synthesize folders;
@synthesize feeds;
@synthesize prefs;
@synthesize feeditems;
@synthesize delegate;

- (id)initWithAuth:(NSString *)authcode {

	if (self = [super init]) {
		
		// initialize local resources.
		_actionList = nil;
		_currentAction = 0;
		
		// maxcount it's a known value, but we'll update it just in case.
		_maxcount = kMaxCountDefault;
		
		allunreads = [[NSString alloc] initWithString:@""];
		tags = [[NSMutableDictionary alloc] init];
		folders = [[NSMutableDictionary alloc] init];
		feeds = [[NSMutableDictionary alloc] init];
		prefs = [[NSMutableDictionary alloc] init];
		feeditems = [[NSMutableDictionary alloc] init];

		// set super values.
		auth = authcode;
		service = self;
	}
	
	return self;
}

#pragma mark -
#pragma mark Public methods

- (RequestAction)requestStatus {

	return (RequestAction)[_actionList objectAtIndex:_currentAction];
}

- (void)getAllData {
	
	// get it all.
	[self _setRequestActionList:RequestActionGetSubs,
								RequestActionGetTags,
								RequestActionGetStreamPrefs,
								RequestActionUnreadCount,
								nil];
	
	// yippee ki-yay motherfucka'
	[self _performActions];
}

- (void)refreshTagList {
	
	// clear local data.
	tags = [[NSMutableDictionary alloc] init];
	
	// if the action list was already defined, it won't do nothing.
	[self _setRequestActionList:RequestActionGetTags, nil];

	// create request url.
	NSString * url = [NSString stringWithFormat:@"%@?output=%@",
					  @"http://www.google.com/reader/api/0/tag/list",
					  @"json"];
	
	// submit.
	[super _submitWithURL:url andMethod:HTTPMethodGet];
}

- (void)refreshSubsList {
	
	// clear local data.
	feeds = [[NSMutableDictionary alloc] init];
	folders = [[NSMutableDictionary alloc] init];

	// if the action list was already defined, it won't do nothing.
	[self _setRequestActionList:RequestActionGetSubs, nil];
	
	// create request url.
	NSString * url = [NSString stringWithFormat:@"%@?output=%@",
					  @"http://www.google.com/reader/api/0/subscription/list",
					  @"json"];
	
	// submit.
	[super _submitWithURL:url andMethod:HTTPMethodGet];
}

- (void)refreshStreamPrefs {
	
	// clear local data.
	prefs = [[NSMutableDictionary alloc] init];
	
	// if the action list was already defined, it won't do nothing.
	[self _setRequestActionList:RequestActionGetStreamPrefs, nil];
	
	// create request url.
	NSString * url = [NSString stringWithFormat:@"%@?output=%@",
					  @"http://www.google.com/reader/api/0/preference/stream/list",
					  @"json"];
	
	// submit.
	[super _submitWithURL:url andMethod:HTTPMethodGet];
}

- (void)refreshUnreadCounts {
	
	// if the action list was already defined, it won't do nothing.
	[self _setRequestActionList:RequestActionUnreadCount, nil];
	
	// create request url.
	NSString * url = [NSString stringWithFormat:@"%@?output=%@",
					  @"http://www.google.com/reader/api/0/unread-count",
					  @"json"];
	
	// submit.
	[super _submitWithURL:url andMethod:HTTPMethodGet];
}

- (GRTagIdCollection)collectionForTagId:(NSString *)tagId {
	
	GRTagIdType type = [GoogleReaderHelper typeForTagId:tagId];
	
	if (type == GRTagIdTypeFeed) {
		
		// nothing to do here, it's a feed.
		return GRTagIdCollectionFeed;
	}
	else if (type == GRTagIdTypeLabel) {
		
		// here we can have a tag or a folder.
		if ([folders objectForKey:tagId] != nil) {
			
			// tagid is a folder.
			return GRTagIdCollectionFolder;
		}
		else if ([tags objectForKey:tagId] != nil) {
			
			// tagid is a tag.
			return GRTagIdCollectionTag;
		}
	}
	
	return GRTagIdCollectionUnknown;
}

- (void)getItemsForFeed:(NSString *)feedid {
	
	// if the action list was already defined, it won't do nothing.
	[self _setRequestActionList:RequestActionFeedItems, nil];
	
	// create request url.
	NSString * url = [NSString stringWithFormat:@"%@%@?n=%d",
					  @"http://www.google.com/reader/api/0/stream/contents/",
					  feedid,
					  kFeedItemsDefaultCount];
	
	// submit.
	[super _submitWithURL:url andMethod:HTTPMethodGet];
}

#pragma mark -
#pragma mark Private methods

- (void)_performActions {

	RequestAction _action = (RequestAction)[[_actionList objectAtIndex:_currentAction] intValue]; 
	
	// we're not going to do something, just notify and go back to defaults.
	if (_action == RequestActionIdle) {
		
		// back to defaults.
		[_actionList release]; _actionList = nil;
		_currentAction = (NSUInteger)RequestActionIdle;
		
		// notify delegate.
		[delegate requestFinished];
		
		// uncomment to debug local data after finished with all requests.
		NSLog(@"%@",feeds);
		NSLog(@"%@",folders);
		NSLog(@"%@",tags);
		
		return;
	}
	
	// notify we're going to perform the request.
	if ([delegate respondsToSelector:@selector(requestWillStartWithAction:)]) {
		
		[delegate requestWillStartWithAction:_action];
	}
	
	// perform the action.
	switch (_action) {
		case RequestActionGetSubs:
			[self refreshSubsList];
			break;
		case RequestActionGetTags:
			[self refreshTagList];
			break;
		case RequestActionGetStreamPrefs:
			[self refreshStreamPrefs];
			break;
		case RequestActionUnreadCount:
			[self refreshUnreadCounts];
			break;
		default:
			break;
	}
}

- (void)_setRequestActionList:(RequestAction)firstAction, ... {
	
	// if a list it's already defined, do not modify it and move forward.
	if (_actionList != nil) {
		return;
	}
	
	RequestAction eachAction;
	va_list argumentList;
	
	// The first argument isn't part of the varargs list
	if (firstAction) {
		
		NSMutableArray * _actions = [NSMutableArray array];
		
		// add first action.
		[_actions addObject:[NSNumber numberWithInt:firstAction]];
		
		// Start scanning for arguments after firstAction.
		va_start(argumentList, firstAction);
		
		// As many times as we can get an argument of type "RequestAction"
		while (eachAction = va_arg(argumentList, RequestAction)) {
		
			// that isn't nil, add it to self's contents.
			[_actions addObject:[NSNumber numberWithInt:eachAction]];
		}
			               
		va_end(argumentList);
		
		// we'll add idle action at the end by detault.
		[_actions addObject:[NSNumber numberWithInt:RequestActionIdle]];
		
		// set the actions we're going to perform..
		_actionList = [[NSArray arrayWithArray:_actions] retain];
	}
}

- (void)_processResponse:(NSDictionary *)dict {
	
	RequestAction _action = (RequestAction)[[_actionList objectAtIndex:_currentAction] intValue]; 

	// process response based on executed action.
	switch (_action) {
		case RequestActionGetTags:
			[self _updateTags:dict];
			break;
		case RequestActionGetSubs:
			[self _updateSubs:dict];
			break;
		case RequestActionGetStreamPrefs:
			[self _updateStreamPrefs:dict];
			break;
		case RequestActionUnreadCount:
			[self _updateUnreadCounts:dict];
			break;
		case RequestActionFeedItems:
			[self _updateFeedItems:dict];
			break;

		default:
			break;
	}
}

- (void)_updateTags:(NSDictionary *)dict {
	
	NSArray * _tags = [dict objectForKey:@"tags"];
	for (NSDictionary * item in _tags) {
		
		NSString * tagid = [item objectForKey:@"id"];
		NSString * sortid = [item objectForKey:@"sortid"];
		
		// get folder for current id.
		NSMutableDictionary * _folder = [folders objectForKey:tagid];
		
		// check if the tag is not a folder.
		if (_folder == nil) {
			
			GRTagIdType type = [GoogleReaderHelper typeForTagId:tagid];
			
			// add only label ids.
			if (type == GRTagIdTypeLabel) {
			
				NSMutableDictionary * _tag = [NSMutableDictionary dictionary];
				
				// get label from id.
				NSArray * _id = [tagid componentsSeparatedByString:@"/"];
				NSString * label = [_id objectAtIndex:3];
				
				// add tag into tags list.
				[_tag setObject:label forKey:@"label"];
				[_tag setObject:sortid forKey:@"sortid"];
				
				[tags setObject:_tag forKey:tagid];
			}
		}
		else {

			// add sortid into folder.
			[_folder setObject:sortid forKey:@"sortid"];
		}
	}
}

- (void)_updateSubs:(NSDictionary *)dict {

	NSArray * _feeds = [dict objectForKey:@"subscriptions"];
	for (NSDictionary * item in _feeds) {
		
		// create a feed object.
		NSMutableDictionary * _feed = [NSMutableDictionary dictionary];
		
		[_feed setObject:[item objectForKey:@"title"] forKey:@"title"];
		[_feed setObject:[item objectForKey:@"sortid"] forKey:@"sortid"];
		[_feed setObject:@"" forKey:@"count"];
		
		// feed has categories associated.
		NSArray * cats = [item objectForKey:@"categories"]; 
		if ([cats count] > 0) {
			
			// the category is a tag used to contain feeds, so it's a folder.
			for (NSDictionary * tag in cats) {
				
				NSString * tagid = [tag objectForKey:@"id"];
				
				// check if folder already exists.
				if ([folders objectForKey:tagid] == nil) {
					
					NSMutableDictionary * _folder = [NSMutableDictionary dictionary];
					
					[_folder setObject:[tag objectForKey:@"label"] forKey:@"label"];
					[_folder setObject:[NSMutableDictionary dictionary] forKey:@"subscriptions"];
					[_folder setObject:@"" forKey:@"count"];
					
					[folders setObject:_folder forKey:tagid];
				}
			}
			
			// now that we've the folders, put the items inside it.
			for (NSDictionary * tag in cats) {
				
				NSMutableDictionary * _folder = [folders objectForKey:[tag objectForKey:@"id"]];
				
				// folder was already added, so put the feed inside it.
				if (_folder != nil) {
					
					[[_folder objectForKey:@"subscriptions"] setObject:_feed
																forKey:[item objectForKey:@"id"]];
				}
			}
		}
		else {
			
			// no categories, so add to feed list.
			[feeds setObject:_feed forKey:[item objectForKey:@"id"]];
		}
	}
}

- (void)_updateStreamPrefs:(NSDictionary *)dict {

	// we'll use this flag to know if the user has root node preferences or not.
	BOOL rootnodeprefs = NO;
	
	// iterate though the different tags on user prefs.
	NSMutableDictionary * _prefs = [dict objectForKey:@"streamprefs"];
	for (NSString * tagid in _prefs) {
		
		NSArray * item = [_prefs objectForKey:tagid];
		GRTagIdType type = [GoogleReaderHelper typeForTagId:tagid];
		
		// check first if the tagid is known by us.
		if (type != GRTagIdTypeUnknown) {
			
			NSString * subsorder = nil;
			GRTagIdCollection collection = [self collectionForTagId:tagid];
			
			// we've to check if the tagid is part of a collection.
			if (collection == GRTagIdCollectionUnknown) {
				
				if (type == GRTagIdTypeStateGoogleRoot) {
					
					subsorder = [[item objectAtIndex:0] objectForKey:@"value"];
				}
				else {
					// it's unknown, so it's crap from sometime ago.
					continue;
				}
			}
			else {
				
				// get subscription ordering node.
				if (type == GRTagIdTypeLabel) {
					
					// search for order preference for current tagid, it's better handle on this way since for some
					// cases like labels, or tags coverted into folders the order node might not be present.
					for (NSDictionary * _pref in item) {
						
						NSString * _id = [_pref objectForKey:@"id"];
						
						// check if attribute is subscription-ordering.
						if ([_id isEqualToString:@"subscription-ordering"]) {
							
							subsorder = [_pref objectForKey:@"value"];
						}
					}
				}
			}
			
			// create label node.
			NSMutableArray * labelids = [NSMutableArray array];
			
			// set a array of objects to search into.
			NSArray * list = [NSArray array];
			
			if (type == GRTagIdTypeStateGoogleRoot) {
				
				list = [NSArray arrayWithObjects:tags, feeds, folders, nil];
			}
			else {
				
				NSDictionary * folder = [folders objectForKey:tagid];
				list = [NSArray arrayWithObjects:[folder objectForKey:@"subscriptions"], nil];
			}
			
			// check if we've a subscription-ordering defined for the current item.
			if (subsorder != nil) {
				
				// get order list.
				NSArray * sortlist = [GoogleReaderHelper splitSortIdsFromString:subsorder];
				for (NSString * sortid in sortlist) {
					
					NSString * _id = @"";
					
					for (NSDictionary * item in list) {
						
						// search for sortid on list item.
						_id = [GoogleReaderHelper tagIdforSortId:sortid onItemsList:item];
						
						// if found, add it to the rootids list.
						if ([_id length] > 0) {
							
							[labelids addObject:_id];
							break;
						}
					}
				}
			}
			else {
				
				// there's only an item on the list array on this case.
				NSDictionary * item = [list objectAtIndex:0];
				
				// there's no order defined, just add the id's on the folder.
				for (NSString * _id in item) {
					
					[labelids addObject:_id];
				}
			}
			
			// we've to save the tagid as a root node, or as a label containing another tags.
			if (type == GRTagIdTypeLabel) {
				
				// save into prefs dictionary
				[prefs setObject:labelids forKey:tagid];
			}
			else if (type == GRTagIdTypeStateGoogleRoot) {
				
				// user has root node prefs, so we don't need to add all their stuff without ordering.
				rootnodeprefs = YES;
				
				// here's the part where we start to hate google.
				list = [NSArray arrayWithObjects:feeds, tags, nil];
				
				// we've to add remaining tags and subs on the root node on the case they were
				// not on the streamprefs.
				for (NSDictionary * item in list) {
					
					for (NSString * _id in item) {
						
						BOOL match = NO;
						
						for (NSString * __id in labelids) {
							
							// tagid already exists on stremprefs node.
							if ([_id isEqualToString:__id]) {
								
								match = YES;
								break;
							}
						}
						
						// we didn't find it on the prefs, so let's add it.
						if (match == NO) {
							[labelids addObject:_id];
						}
					}
					
					// save into prefs dictionary
					[prefs setObject:labelids forKey:kStreamPrefsRootNode];
				}
			}
		}
	}
	
	// this case applies for users without a streamprefs root node.
	if (rootnodeprefs == NO) {
		
		// we still have google here.
		NSArray * list = [NSArray arrayWithObjects:folders, feeds, tags, nil];
		
		// create label node.
		NSMutableArray * labelids = [NSMutableArray array];
		
		// we've to add all the stuff user have since we don't have order preferences.
		for (NSDictionary * item in list) {
			
			for (NSString * _id in item) {
				
				[labelids addObject:_id];
			}
		}
		
		// save into prefs dictionary
		[prefs setObject:labelids forKey:kStreamPrefsRootNode];
	}
	
	NSLog(@"%@", prefs);
}

- (void)_updateUnreadCounts:(NSDictionary *)dict {
	
	// update maxcount, just in case it's updated on the API.
	_maxcount = [[dict objectForKey:@"max"] intValue];
	
	// update unread-counts.
	NSArray * _unread = [dict objectForKey:@"unreadcounts"];
	for (NSDictionary * item in _unread) {
		
		NSString * tagid = [item objectForKey:@"id"];
		NSString * count = [GoogleReaderHelper unreadStringFromNumber:[item objectForKey:@"count"]
														  forMaxCount:_maxcount];
		
		GRTagIdType type = [GoogleReaderHelper typeForTagId:tagid];
		if (type == GRTagIdTypeFeed) {
			
			// we'll have to check if sub it's not contained on folders list.
			if ([feeds objectForKey:tagid] == nil) {
				
				// iterate though folder list.
				for (NSString * _folderid in folders) {
					
					NSDictionary * _folder = [folders objectForKey:_folderid];
					NSDictionary * _feeds = [_folder objectForKey:@"subscriptions"];
					
					if ([_feeds objectForKey:tagid] != nil) {
					
						NSDictionary * _feed = [_feeds objectForKey:tagid];
						
						// update count.
						[_feed setValue:count forKey:@"count"];
						
						break;
					}
				}
			}
			else {
				NSDictionary * _feed = [feeds objectForKey:tagid];
				
				// update count.
				[_feed setValue:count forKey:@"count"];
			}
		}
		else if (type != GRTagIdTypeUnknown) {
			
			// update folder it tag is a label.
			if (type == GRTagIdTypeLabel) {
				
				NSDictionary * _folder = [folders objectForKey:tagid];
				
				// update count.
				[_folder setValue:count forKey:@"count"];
			}
			else if (type == GRTagIdTypeStateGoogleReadingList) {
				
				// update all unreads value.
				allunreads = [count copy];
			}
		}
	}
}

- (void)_updateFeedItems:(NSDictionary *)dict {
	
	// current feed id.
	NSString * feedid = [dict objectForKey:@"id"];
	
	// check if feedid already exists on feeditems object.
	if ([feeditems objectForKey:feedid] == nil) {
		
		// create dictionary for feedid.
		NSMutableDictionary * feed = [NSMutableDictionary dictionary];
		
		// save some feed information.
		[feed setObject:[dict objectForKey:@"title"] forKey:@"title"];
		[feed setObject:[dict objectForKey:@"continuation"] forKey:@"continuation"];
		
		// save last updated time.
		[feed setObject:[dict objectForKey:@"updated"] forKey:@"updated"];
		
		// create items array.
		NSMutableArray * items = [NSMutableArray array];
		
		// iterate through the items list and add it into the items array.
		for (NSDictionary * item in [dict objectForKey:@"items"]) {
			
			// we'll create a new dictionary to save only the item information we're interested in.
			NSMutableDictionary * _item = [NSMutableDictionary dictionary];
			
			// save feed item data.
			[_item setObject:[item objectForKey:@"id"] forKey:@"id"];
			[_item setObject:[item objectForKey:@"title"] forKey:@"title"];
			
			// save feed published time.
			[_item setObject:[item objectForKey:@"published"] forKey:@"published"];
			
			// save feed source url.
			NSDictionary * alternate = [[item objectForKey:@"alternate"] objectAtIndex:0];
			NSString * url = [alternate objectForKey:@"href"];
			[_item setObject:url forKey:@"href"];
			
			// content node can come on wrapped by a content or summary node.
			NSDictionary * content;
			if ([item objectForKey:@"content"] != nil) {
				content = [item objectForKey:@"content"];
			}
			else {
				content = [item objectForKey:@"summary"];
			}
			
			NSString * _content = [content objectForKey:@"content"];
			
			// save feed html content.
			[_item setObject:_content forKey:@"content"];
			
			// save feed detail for showing on item list.
			[_item setObject:[FormatHelper feedDetailForString:_content] forKey:@"detail"];
			
			// save feed author if available.
			NSString * author;
			if ([item objectForKey:@"author"] != nil) {
				
				author = [item objectForKey:@"author"];
			}
			else {
				author = @"Anonymous";
			}

			// save author.
			[_item setObject:author forKey:@"author"];
			
			// add item.
			[items addObject:_item];
		}
		
		// save all items for feed.
		[feed setObject:items forKey:@"items"];
		
		// add dictionary into feeditems object.
		[feeditems setObject:feed forKey:feedid];
	}
	
	// we still have to handle here on the case the feed already have items.
	
	NSLog(@"%@",feeditems);
}

#pragma mark -
#pragma mark ASIHTTPRequestDelegate methods

- (void)requestFinished:(ASIHTTPRequest *)request {
	
	// get response string.
	NSString * resp = [request responseString];
	
	// parse response.
	NSDictionary * dict = [resp JSONValue];
	
	// process response.
	[self _processResponse:dict];
	
	// set next action to perform.
	_currentAction++;
	
	// execute the next action.
	[self _performActions];
}

- (void)requestFailed:(ASIHTTPRequest *)request {
	
	return;
}

#pragma mark -

- (void)dealloc {
	
	[allunreads release];
	[tags release];
	[folders release];
	[feeds release];
	[prefs release];
	[feeditems release];
	[delegate release];
	[super dealloc];
}

@end
